package de.uni_stuttgart.ipvs.ids.socketServer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.rmi.RemoteException;

import de.uni_stuttgart.ipvs.ids.summationImpl.SummationSession;

/**
 * Thread for handling a connected client socket.
 * @author Team 3/4 - Elkhan Dadashov, Dhaval A Shah, Patrick Stortz
 */
public class SummationSessionThread implements Runnable {

	/** The name to be used to identify this instance */
	private String mName = "Session Thread";
	/** The TCP socket connection for this service thread */
	private Socket mClientSocket;
	/** Indicates whether we are connected to the client socket or not */
	private boolean mConnected = false;

	/** The PrintWriter to be used to write to the socket output stream */
	private PrintWriter mOutputWriter = null;
	/** The BufferedReader to be use to read from the socket input stream */
	private BufferedReader mInputReader = null;

	/** The instance of the SummationSession to use */
	private SummationSession summationSession = null;


	/** @return the Name to identify this Runnable */
	public String getName() {
		return mName;
	}

	/** @param mName the Name to identify this Runnable to set */
	public void setName(String mName) {
		this.mName = mName;
	}

	/**
	 * Creates an new session thread for a client.
	 * @param clientSocket the socket connection to the client.
	 */
	public SummationSessionThread(Socket clientSocket) {
		this.setName(this.getName() + " (" + clientSocket.getRemoteSocketAddress() + ")");
		mClientSocket = clientSocket; 
		System.out.println(this.getName()+" | Got the client socket: " + this.mClientSocket.getRemoteSocketAddress());// DEBUG LOG OUTPUT
	}

	/**
	 * Handles the connection to a client
	 */
	public void run() {
		System.out.println(this.getName()+" | Processing a socket connection...");// DEBUG LOG OUTPUT

		if(mClientSocket!=null&&mClientSocket.isConnected()){		

			// get the output writer
			try {
				System.out.println(this.getName()+" | Getting the output writer...");// DEBUG LOG OUTPUT
				mOutputWriter = new PrintWriter(mClientSocket.getOutputStream(), true);
			} catch (IOException e) {
				System.err.println(this.getName()+" | Could not get the output writer! Exception: " + e.getMessage());// DEBUG LOG OUTPUT
				mOutputWriter=null;
			}

			// get the input reader
			try {
				System.out.println(this.getName()+" | Getting the input reader...");// DEBUG LOG OUTPUT
				mInputReader = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
			} catch (IOException e) {
				System.err.println(this.getName()+" | Could not get the input reader! Exception: " + e.getMessage());// DEBUG LOG OUTPUT
				mInputReader=null;
			}

			// handle the protocol with the client
			if(mOutputWriter!=null&&mInputReader!=null){
				// we are connected
				mConnected = true;
				// create a summation session instance
				try {
					System.out.println(this.getName()+" | Creating a summation session instance...");// DEBUG LOG OUTPUT
					summationSession = new SummationSession();
				} catch (RemoteException e) {
					System.err.println(this.getName()+" | Could not create a summation session instance! Exception: " + e.getMessage());// DEBUG LOG OUTPUT
				}

				if(summationSession!=null){
					// tell the client that we are ready
					System.out.println(this.getName()+" | Sending ready signal to client " + this.mClientSocket.getRemoteSocketAddress()+"...");// DEBUG LOG OUTPUT
					this.sendString("READY");
					// start to receive requests from the client
					while( mClientSocket.isClosed()==false && mConnected==true ){
						// wait for the request and process it
						//System.out.println(this.getName()+" | Waiting for a request of the client...");// DEBUG LOG OUTPUT
						String request = this.receiveString();
						processRequest(request);
					}
				}
			}
			
			System.out.println(this.getName()+" | Session done for client " + this.mClientSocket.getRemoteSocketAddress()+".");// DEBUG LOG OUTPUT

			// close the output writer
			if(mOutputWriter!=null) {
				System.out.println(this.getName()+" | Closing the output writer...");// DEBUG LOG OUTPUT
				mOutputWriter.close();
				mOutputWriter=null;
			}
			
			// close the input reader
			if(mInputReader!=null){
				try {
					System.out.println(this.getName()+" | Closing the input reader...");// DEBUG LOG OUTPUT
					mInputReader.close();
					mInputReader=null;
				} catch (IOException e) {
					System.err.println(this.getName()+" | Could not close the input reader! Exception: " + e.getMessage());// DEBUG LOG OUTPUT
				}
			}

			// close the socket
			if(mClientSocket!=null) {
				try {
					System.out.println(this.getName()+" | Closing the socket...");// DEBUG LOG OUTPUT
					mClientSocket.close();
					mClientSocket=null;	
					mConnected = false;
				} catch (IOException e) {
					System.err.println(this.getName()+" | Could not close its socket to a client! Exception: " + e.getMessage());// DEBUG LOG OUTPUT
				}
			}
		}

		System.out.println(this.getName()+" | Socket connection processed.");// DEBUG LOG OUTPUT
	}

	/**
	 * Processes a client request.
	 * @param request the received request to process.
	 */
	private void processRequest(String request){
		if(request!=null){
			// check and process the request
			//System.out.println(this.getName()+" | Processing following request of the client: " + request);// DEBUG LOG OUTPUT
			String reply = null;
			boolean success = true;
			if(request.endsWith(" ") || request.endsWith("\n")){
				request = request.trim().replace("\n", "");
				if(request.equals("SUM")){
					try {
						// we got SUM as request, get the sum and send it
						int sum = summationSession.getSum();
						reply = "OK" + " " + request + " " + sum;
						success = true;
					} catch (RemoteException e) {
						success = false;
						System.err.println(this.getName()+" | Error - Failed to get the sum!");// DEBUG LOG OUTPUT
					}
				}else{
					try{
						// try to parse the request as integer
						int number = Integer.parseInt(request);
						// we got a valid integer as request
						try {
							// add the integer to the sum and ack the request
							summationSession.add(number);
							reply = "OK" + " " + request;
							success = true;
						} catch (RemoteException e) {
							// cannot add the integer to the sum
							success = false;
							reply = "ERR" + " " + request;
							System.err.println(this.getName()+" | Error - Failed to add the integer to the sum!");// DEBUG LOG OUTPUT
						}
					}catch(NumberFormatException ex){
						// request is not a sum nor a valid integer
						success = false;
						reply = "ERR" + " " + request;
						System.err.println(this.getName()+" | Error - Request is not SUM nor an integer.");// DEBUG LOG OUTPUT
					}
				}
			}else{
				// request does not have a valid delimiter
				success = false;
				reply = "ERR" + " " + request;
				System.err.println(this.getName()+" | Error - Request does not contain a valid delimiter.");// DEBUG LOG OUTPUT
			}

			// send the created reply to client
			this.sendString(reply);

			if(success){
				//System.out.println(this.getName()+" | Request of the client successfully processed.");// DEBUG LOG OUTPUT
			}else{
				System.err.println(this.getName()+" | Failed to process the request of the client!");// DEBUG LOG OUTPUT
			}
		}
	}

	/**
	 * Receives a string from the client socket.
	 * @return the string received from the client socket.
	 */
	private String receiveString(){
		String received = null;
		if(mInputReader!=null){
			try {
				//System.out.println(this.getName()+" | Reading from the input stream...");// DEBUG LOG OUTPUT
				received = mInputReader.readLine();
				//System.out.println(this.getName()+" | Read following message from input stream: '"+received+"'.");// DEBUG LOG OUTPUT
			} catch (IOException e) {
				// we cannot read from the stream anymore --> means the connection got closed by the client, we are done!
				mConnected=false;
				received=null;
			}
		}
		return received;
	}

	/**
	 * Sends a string to the client socket.
	 * @param string the string to send to the client socket.
	 */
	private void sendString(String string){
		if(mOutputWriter!=null){
			//System.out.println(this.getName()+" | Writing to the output stream...");// DEBUG LOG OUTPUT
			mOutputWriter.println(string);
			//System.out.println(this.getName()+" | Wrote following message to the output stream: '"+string+"'.");// DEBUG LOG OUTPUT
		}
	}
}
